(function ($, window, document, undefined) {

$(document).ready(function() {
	
	// Set your preferneces here:
	var testErrors = true,
		logObjects = true;
	
	$klass = $.klass;
	$proto = $klass.prototype;
	
	module("Klass");
	
	test("New classes without parent", 3, function() {
		var methods_T = {
				init: function(show) { },
				f: function() {
					return false;
				},
				_klass: {
					n: "T"
				}
			},
			methods_T1 = $.extend(true, {}, methods_T),
			methods_T2 = $.extend(true, {}, methods_T),
			methods_T3 = $.extend(true, {}, methods_T);
		
		// Those should not throw any error and have the same result (Be careful: never make multiple class from the same exact function in real use!!!):
		T = $klass(methods_T);
		T1 = $klass(null, methods_T1);
		T2 = $klass(undefined, methods_T2);
		T3 = $klass(false, methods_T3);
		
		ok(klassEqual(T, T1), 'klassEqual(T, T1)');
		ok(klassEqual(T, T2), 'klassEqual(T, T2)');
		ok(klassEqual(T, T3), 'klassEqual(T, T3)');
	});
	
	test("Classes hierarchy", function() {
		var methods = {
			A: {
				init: function(show) {
					if(show) console.log("A");
				},
				f: function() {
					return this.init._klass.klass.n + "." + arguments.callee._klass.name;
				},
				ping: function(msg) {
					return "ping: " + msg;
				},
				_klass: {
					n: "A"
				}
			},
			AA: {
				init: function(show) {
					if(show) console.log("AA");
					this._super(arguments, show);
				},
				_klass: {
					n: "AA"
				}
			},
			AAA: {
				init: function(show) {
					if(show) console.log("AAA");
					this._super(arguments, show);
				},
				f: function() {
					return this._super(arguments) + "+";
				},
				ping: function() {
					return "ping disabled !";
				},
				tryPing: function(useSuper) {
					if(useSuper) return this._super("ping", arguments, "hello");
					else return this.ping("hello");
				},
				_klass: {
					n: "AAA"
				}
			}
		}
		
		A = $klass(methods.A);
		AA = $klass(A, methods.AA);
		AAA = $klass(AA, methods.AAA);
		a = new A();
		aa = new AA();
		console.startRecordingLogs(false);
		aaa = new AAA(true);	// Will print "AAA", "AA" and "A" in the console
		strictEqual(console.stopRecordingLogs().join("\n"), "AAA\nAA\nA", 'aaa = new AAA(true)');
		
		ok(a instanceof $klass, 'a instanceof $klass');
		ok(aa instanceof $klass, 'aa instanceof $klass');
		ok(aaa instanceof $klass, 'aaa instanceof $klass');
		
		ok(aaa instanceof AAA, 'aaa instanceof AAA');
		ok(aaa instanceof AA, 'aaa instanceof AA');
		ok(aaa instanceof A, 'aaa instanceof A');
		ok(!(aa instanceof AAA), 'aa not instanceof AAA');
		ok(aa instanceof AA, 'aa instanceof AA');
		ok(aa instanceof A, 'aa instanceof A');
		ok(!(a instanceof AAA), 'a not instanceof AAA');
		ok(!(a instanceof AA), 'a not instanceof AA');
		ok(a instanceof A, 'a instanceof A');
		strictEqual(A._super, undefined, 'A._super');
		strictEqual(AA._super, A, 'AA._super');
		strictEqual(AAA._super, AA, 'AAA._super');
		
		if(logObjects) {
			console.log({A: A, a: a});
			console.log({AA: AA, aa: aa});
			console.log({AAA: AAA, aaa: aaa});
		}
		
		strictEqual(a.f(), "A.f", 'a.f()');
		strictEqual(aa.f(), "AA.f", 'aa.f()');
		strictEqual(aaa.f(), "AAA.f+", 'aaa.f()');
		notStrictEqual(a.f._klass, undefined, 'a.f._klass');
		strictEqual(aa.f._klass, a.f._klass, 'aa.f._klass');
		notStrictEqual(aaa.f._klass, undefined, 'aaa.f._klass');
		notStrictEqual(aaa.f._klass, a.f._klass, 'aaa.f._klass');
		notStrictEqual(aaa.f._klass, aa.f._klass, 'aaa.f._klass');
		strictEqual(a.ping("coucou"), "ping: coucou", 'a.ping("coucou")');
		strictEqual(aaa.tryPing(), "ping disabled !", 'aaa.tryPing()');
		strictEqual(aaa.tryPing(true), "ping: hello", 'aaa.tryPing(true)');
		
		AA.l = "l";
		aa.f = function() {	// This function has not been properly prepared to be used as a method: a call to this._super() will throw an error
			// For Firefox: TypeError: (_klass)|[a-z] is undefined
			// For Safari: 'undefined' is not an object
			// For Opera: Cannot convert '(_klass)|[a-z]' to object
			// [a-z] is useful if klass has been minified
			var errRegExp = /(TypeError: (_klass)|[a-z] is undefined)|('undefined' is not an object)|(Cannot convert '(_klass)|[a-z]' to object)/;
			
			if (testErrors) {
				try {
					this._super(arguments);	// Calling _super will throw an error because arguments.callee._klass === aa.f._klass === undefined
				}
				catch(e) { console.assert(e.toString().match(errRegExp)); }
			}
			var self = this;
			raises(function() {
				self._super(arguments);	// Calling _super will throw an error because arguments.callee._klass === aa.f._klass === undefined
			}, errRegExp, 'Not a true method');
			
			
			return "hehe";
		};
		
		strictEqual(aa.f(), "hehe", 'aa.f()');
		strictEqual(aa.f._klass, undefined, 'aa.f._klass');
		
		
		if(false) {	// Just for the fun and for curiosity:
			klass = new $klass(methods.A);
			console.log({$klass: $klass, klass: klass});
			k = new klass();
			console.log({a: a, k: k}, a.f(), k.f());
		}
	});
	
	
	if(false) {	// Inheriting from other classes doesn't work as we might hope:
		_Date = $klass(Date, {
			init: function(show) {
				console.log("DATE");
				this._super("constructor", arguments);
			},
			n: "DATE"
		});
		date = new Date();
		_date = new _Date();
		console.log({date: date, _date: _date}, date.toString(), _date.toString());
	}
	
	test("Auto added init", function() {
		
		var NoInitParent = $.klass({}),
			NoInitChild = $.klass(NoInitParent, {
				init: function() {
					this._super(arguments);
				}
			}),
			InitParent = $.klass({
				init: function(name) {
					this.name = name;
				}
			}),
			InitChild = $.klass(InitParent, {});
		
		new NoInitChild();	// Should not throw any error
		
		var initObj = new InitChild( "Moi" );
		strictEqual(initObj.name, "Moi", 'initObj.name');
		
	});
	
});

})(jQuery, window, window.document);